import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject, fromEvent } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';

export interface VirtualScrollConfig {
  itemHeight: number;           // 每行默认高度
  containerHeight: number;      // 容器高度
  buffer: number;              // 缓冲区行数（上下各多渲染几行）
  overscan: number;            // 预渲染行数（防止快速滚动时白屏）
  enableDynamicHeight: boolean; // 是否启用动态行高
  scrollDebounceTime: number;   // 滚动防抖时间
}

export interface VirtualScrollState {
  scrollTop: number;           // 当前滚动位置
  visibleStartIndex: number;   // 可见区域起始索引
  visibleEndIndex: number;     // 可见区域结束索引
  renderStartIndex: number;    // 实际渲染区域起始索引
  renderEndIndex: number;      // 实际渲染区域结束索引
  totalHeight: number;         // 总内容高度
  offsetY: number;            // 当前渲染内容的Y轴偏移
  viewportHeight: number;      // 视口高度
}

export interface VirtualItem {
  index: number;              // 原始数据索引
  height: number;             // 项目高度
  top: number;               // 距离顶部的位置
  bottom: number;            // 距离顶部的底部位置
}

@Injectable({
  providedIn: 'root'
})
export class VirtualScrollService {
  private config: VirtualScrollConfig = {
    itemHeight: 24,
    containerHeight: 600,
    buffer: 5,
    overscan: 10,
    enableDynamicHeight: true,
    scrollDebounceTime: 16  // 约60fps
  };

  private items: VirtualItem[] = [];
  private measuredHeights = new Map<number, number>();
  private scrollElement: HTMLElement | null = null;
  private resizeObserver: ResizeObserver | null = null;

  // 状态流
  private stateSubject = new BehaviorSubject<VirtualScrollState>({
    scrollTop: 0,
    visibleStartIndex: 0,
    visibleEndIndex: 0,
    renderStartIndex: 0,
    renderEndIndex: 0,
    totalHeight: 0,
    offsetY: 0,
    viewportHeight: 0
  });

  // 滚动事件流
  private scrollSubject = new Subject<number>();

  constructor() {
    // 设置滚动防抖
    this.scrollSubject.pipe(
      debounceTime(this.config.scrollDebounceTime),
      distinctUntilChanged()
    ).subscribe(scrollTop => {
      this.updateVirtualState(scrollTop);
    });
  }

  /**
   * 初始化虚拟滚动
   */
  initialize(config: Partial<VirtualScrollConfig>, totalItems: number): void {
    this.config = { ...this.config, ...config };
    this.initializeItems(totalItems);
    this.updateVirtualState(0);
  }

  /**
   * 设置滚动容器元素
   */
  setScrollElement(element: HTMLElement): void {
    this.cleanup();
    this.scrollElement = element;
    this.setupScrollListener();
    this.setupResizeObserver();
  }

  /**
   * 获取虚拟滚动状态流
   */
  getState(): Observable<VirtualScrollState> {
    return this.stateSubject.asObservable();
  }

  /**
   * 获取当前状态
   */
  getCurrentState(): VirtualScrollState {
    return this.stateSubject.value;
  }

  /**
   * 更新项目数量
   */
  updateItemCount(count: number): void {
    this.initializeItems(count);
    this.updateVirtualState(this.stateSubject.value.scrollTop);
  }

  /**
   * 设置指定项目的高度
   */
  setItemHeight(index: number, height: number): void {
    if (this.config.enableDynamicHeight) {
      this.measuredHeights.set(index, height);
      this.updateItemPositions();
      this.updateVirtualState(this.stateSubject.value.scrollTop);
    }
  }

  /**
   * 批量设置项目高度
   */
  setItemHeights(heights: Map<number, number>): void {
    if (this.config.enableDynamicHeight) {
      heights.forEach((height, index) => {
        this.measuredHeights.set(index, height);
      });
      this.updateItemPositions();
      this.updateVirtualState(this.stateSubject.value.scrollTop);
    }
  }

  /**
   * 滚动到指定索引
   */
  scrollToIndex(index: number, alignment: 'start' | 'center' | 'end' = 'start'): void {
    if (!this.scrollElement || index < 0 || index >= this.items.length) {
      return;
    }

    const item = this.items[index];
    let scrollTop = item.top;

    switch (alignment) {
      case 'center':
        scrollTop = item.top - (this.config.containerHeight - item.height) / 2;
        break;
      case 'end':
        scrollTop = item.bottom - this.config.containerHeight;
        break;
    }

    scrollTop = Math.max(0, Math.min(scrollTop, this.getTotalHeight() - this.config.containerHeight));
    this.scrollElement.scrollTop = scrollTop;
  }

  /**
   * 获取总高度
   */
  getTotalHeight(): number {
    return this.items.length > 0 ? this.items[this.items.length - 1].bottom : 0;
  }

  /**
   * 清理资源
   */
  cleanup(): void {
    if (this.resizeObserver) {
      this.resizeObserver.disconnect();
      this.resizeObserver = null;
    }
    this.scrollElement = null;
  }

  private initializeItems(count: number): void {
    this.items = [];
    let top = 0;

    for (let i = 0; i < count; i++) {
      const height = this.getItemHeight(i);
      this.items.push({
        index: i,
        height,
        top,
        bottom: top + height
      });
      top += height;
    }
  }

  private getItemHeight(index: number): number {
    if (this.config.enableDynamicHeight && this.measuredHeights.has(index)) {
      return this.measuredHeights.get(index)!;
    }
    return this.config.itemHeight;
  }

  private updateItemPositions(): void {
    let top = 0;
    for (const item of this.items) {
      item.height = this.getItemHeight(item.index);
      item.top = top;
      item.bottom = top + item.height;
      top += item.height;
    }
  }

  private setupScrollListener(): void {
    if (!this.scrollElement) return;

    fromEvent(this.scrollElement, 'scroll').subscribe(() => {
      this.scrollSubject.next(this.scrollElement!.scrollTop);
    });
  }

  private setupResizeObserver(): void {
    if (!this.scrollElement || !window.ResizeObserver) return;

    this.resizeObserver = new ResizeObserver(entries => {
      for (const entry of entries) {
        const newHeight = entry.contentRect.height;
        if (newHeight !== this.config.containerHeight) {
          this.config.containerHeight = newHeight;
          this.updateVirtualState(this.stateSubject.value.scrollTop);
        }
      }
    });

    this.resizeObserver.observe(this.scrollElement);
  }

  private updateVirtualState(scrollTop: number): void {
    const { buffer, overscan } = this.config;
    const containerHeight = this.config.containerHeight;

    // 查找可见区域
    const visibleStartIndex = this.findStartIndex(scrollTop);
    const visibleEndIndex = this.findEndIndex(scrollTop + containerHeight);

    // 计算渲染区域（加上缓冲区和预渲染区域）
    const renderStartIndex = Math.max(0, visibleStartIndex - buffer - overscan);
    const renderEndIndex = Math.min(this.items.length - 1, visibleEndIndex + buffer + overscan);

    // 计算偏移量
    const offsetY = renderStartIndex > 0 ? this.items[renderStartIndex].top : 0;

    // 计算总高度
    const totalHeight = this.getTotalHeight();

    const newState: VirtualScrollState = {
      scrollTop,
      visibleStartIndex,
      visibleEndIndex,
      renderStartIndex,
      renderEndIndex,
      totalHeight,
      offsetY,
      viewportHeight: containerHeight
    };

    this.stateSubject.next(newState);
  }

  private findStartIndex(scrollTop: number): number {
    // 使用二分查找优化性能
    let start = 0;
    let end = this.items.length - 1;

    while (start <= end) {
      const mid = Math.floor((start + end) / 2);
      const item = this.items[mid];

      if (item.bottom <= scrollTop) {
        start = mid + 1;
      } else if (item.top > scrollTop) {
        end = mid - 1;
      } else {
        return mid;
      }
    }

    return Math.max(0, start);
  }

  private findEndIndex(scrollBottom: number): number {
    // 使用二分查找优化性能
    let start = 0;
    let end = this.items.length - 1;

    while (start <= end) {
      const mid = Math.floor((start + end) / 2);
      const item = this.items[mid];

      if (item.top >= scrollBottom) {
        end = mid - 1;
      } else if (item.bottom < scrollBottom) {
        start = mid + 1;
      } else {
        return mid;
      }
    }

    return Math.min(this.items.length - 1, end);
  }
} 